﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using System.Runtime.InteropServices;

namespace DDD {
    /// <summary>
    /// <see cref="VertexArray"/>クラスに拡張メソッドを追加するクラス
    /// </summary>
    public static class VertexArrayExtensions {
        /// <summary>
        /// コンパイルします
        /// </summary>
        /// <remarks>
        /// コンパイルするとGPU側にメモリー領域が確保され(必要なら)データが転送されます。
        /// 何回でも呼び出し可能です｡
        /// </remarks>
        /// <param name="varry">VertexArrayオブジェクト</param>
        public static void Compile (this VertexArray varry) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[varry];
            if (id > 0) {
                return;
            }
            ErrorCode err;

            GL.GenBuffers (1, out id);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glGenBuffers was Failed, err=" + err);
            }

            var vc = varry.VertexCount;
            var cc = varry.ComponentCount;
            var num = vc * cc;
            var type = varry.ComponentType;

            GL.BindBuffer (BufferTarget.ArrayBuffer, id);

            switch (type) {
                case ComponentType.Byte: {
                        var data = new byte[num];
                        var size = (IntPtr)(sizeof (byte) * num);
                        ((VertexArray<byte>)varry).Get (0, vc, data);
                        GL.BufferData (BufferTarget.ArrayBuffer, size, data, BufferUsageHint.StaticDraw);
                        break;
                    }
                case ComponentType.SByte: {
                        var data = new sbyte[num];
                        var size = (IntPtr)(sizeof (sbyte) * num);
                        ((VertexArray<sbyte>)varry).Get (0, vc, data);
                        GL.BufferData (BufferTarget.ArrayBuffer, size, data, BufferUsageHint.StaticDraw);
                        break;
                    }
                case ComponentType.Short: {
                        var data = new short[num];
                        var size = (IntPtr)(sizeof (short) * num);
                        ((VertexArray<short>)varry).Get (0, vc, data);
                        GL.BufferData (BufferTarget.ArrayBuffer, size, data, BufferUsageHint.StaticDraw);
                        break;
                    }
                case ComponentType.UShort: {
                        var data = new ushort[num];
                        var size = (IntPtr)(sizeof (ushort) * num);
                        ((VertexArray<ushort>)varry).Get (0, vc, data);
                        GL.BufferData (BufferTarget.ArrayBuffer, size, data, BufferUsageHint.StaticDraw);
                        break;
                    }
                case ComponentType.Int: {
                        var data = new int[num];
                        var size = (IntPtr)(sizeof (int) * num);
                        ((VertexArray<int>)varry).Get (0, vc, data);
                        GL.BufferData (BufferTarget.ArrayBuffer, size, data, BufferUsageHint.StaticDraw);
                        break;
                    }
                case ComponentType.UInt: {
                        var data = new uint[num];
                        var size = (IntPtr)(sizeof (uint) * num);
                        ((VertexArray<uint>)varry).Get (0, vc, data);
                        GL.BufferData (BufferTarget.ArrayBuffer, size, data, BufferUsageHint.StaticDraw);
                        break;
                    }
                case ComponentType.Float: {
                        var data = new float[num];
                        var size = (IntPtr)(sizeof (float) * num);
                        ((VertexArray<float>)varry).Get (0, vc, data);
                        GL.BufferData (BufferTarget.ArrayBuffer, size, data, BufferUsageHint.StaticDraw);
                        break;
                    }
                default: throw new NotImplementedException ("Sorry");
            }
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glBufferData was failed, err=" + err);
            }

            GL.BindBuffer (BufferTarget.ArrayBuffer, 0);
            cache.Add (varry, id);
        }

        public static void Capture (this VertexArray varry) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[varry];
            if (id == 0) {
                throw new InvalidOperationException ("OpenGL id was not found");
            }
            ErrorCode err;

            var vc = varry.VertexCount;
            var cc = varry.ComponentCount;
            var num = vc * cc;
            var type = varry.ComponentType;

            GL.BindBuffer (BufferTarget.ArrayBuffer, id);

            var ptr = GL.MapBuffer (BufferTarget.ArrayBuffer, BufferAccess.ReadOnly);
            if ((err = GL.GetError ()) > 0) {
                throw new ArgumentException ("glMapBuffer was failed, err=" + err);
            }

            switch (varry.ComponentType) {
                case ComponentType.Byte: {
                        var src = new byte[num];
                        Marshal.Copy (ptr, src, 0, num);
                        ((VertexArray<byte>)varry).Set (0, vc, src);
                        break;
                    }
                case ComponentType.SByte: {
                        var src = new byte[num];
                        Marshal.Copy (ptr, src, 0, num);
                        ((VertexArray<sbyte>)varry).Set (0, vc, src.Select (x => (sbyte)x).ToArray ());
                        break;
                    }
                case ComponentType.Short: {
                        var src = new short[num];
                        Marshal.Copy (ptr, src, 0, num);
                        ((VertexArray<short>)varry).Set (0, vc, src);
                        break;
                    }
                case ComponentType.UShort: {
                        var src = new short[num];
                        Marshal.Copy (ptr, src, 0, num);
                        ((VertexArray<ushort>)varry).Set (0, vc, src.Select (x => (ushort)x).ToArray ());
                        break;
                    }
                case ComponentType.Int: {
                        var src = new int[num];
                        Marshal.Copy (ptr, src, 0, num);
                        ((VertexArray<int>)varry).Set (0, vc, src);
                        break;
                    }
                case ComponentType.UInt: {
                        var src = new int[num];
                        Marshal.Copy (ptr, src, 0, num);
                        ((VertexArray<uint>)varry).Set (0, vc, src.Select (x => (uint)x).ToArray ());
                        break;
                    }
                case ComponentType.Float: {
                        var src = new float[num];
                        Marshal.Copy (ptr, src, 0, num);
                        ((VertexArray<float>)varry).Set (0, vc, src);
                        break;
                    }
                default: throw new NotImplementedException ("Sorry");
            }

            GL.UnmapBuffer (BufferTarget.ArrayBuffer);
            if ((err = GL.GetError ()) > 0) {
                throw new ArgumentException ("glUnmapBuffer was failed, err=" + err);
            }

            GL.BindBuffer (BufferTarget.ArrayBuffer, 0);

        }
    }
}
